/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.ove.openmath.jome.ctrl.mml;

/**
 *
 * @author Mr.Sam
 */
import java.io.*;
import java.util.*;

import fr.ove.openmath.jome.ctrl.mml.*;

/**
* A parser for MathML objects.
*
* @author © 2000 DIRAT Laurent
* @version 1.0 25/05/2000
*/
public abstract class BasicXMLParser {
    private int charRead = -1;
    private StringBuffer buffer = new StringBuffer();
    
    /**
    * Parse the object coming through the source stream.
    * @param source the source stream.
    */
    public void parse(InputStream source) throws IOException {
        Tag tag;
        char c;
        StringTokenizer tokenizer;
        
        while(source.available() != 0) {
            parseSeparator(source);
            c = getChar(source);
            if (c == '<') {
                tag = new Tag();
                parseSeparator(source);
                c = getChar(source);
                if (c == '/') { // Closing tag
                    tag.setIsStart(false);
                    clearBuffer();
                    parseSeparator(source);
                    parseName(source);
                    tag.setName(buffer.toString());
                    parseClosing(source);
                    readTag(tag);
                }
                else if (isTag(c) || isDigit(c)) {
                    clearBuffer();
                    buffer.append(c);
                    parseName(source);
                    tag.setName(buffer.toString());
                    parseSeparator(source);
                    c = getChar(source);
                    if (isTag(c) || isDigit(c)) {
                        while ((c != '>') && (c != '/')) {
                            Attribute att = new Attribute();
                            clearBuffer();
                            buffer.append(c);
                            parseName(source);
                            att.setName(buffer.toString());
                            parseEqual(source);
                            parseQuote(source);
                            clearBuffer();
                            parseName(source);
                            att.setValue(buffer.toString());
                            tag.addAttribute(att);
                            parseQuote(source);
                            parseSeparator(source);
                            c = getChar(source);
                        }
                        
                        if (c == '>')
                            tag.setIsEmpty(false);
                            
                        ungetChar(c);
                        parseClosing(source);    
                        readTag(tag);
                    }
                    else if ((c == '>') || (c == '/')) {
                        if (c == '>')
                            tag.setIsEmpty(false);
                            
                        ungetChar(c);
                        parseClosing(source);
                        readTag(tag);
                    }
                    else
                        System.out.println("Dommage !!!!");
                }
                else
                    System.out.println("Dommage !!!!");
            }
            else if (isTag(c) || isDigit(c)) {
                clearBuffer();
                buffer.append(c);
                c = getChar(source);
                while (c != '<') {
                    buffer.append(c);
                    c = getChar(source);
                }
                ungetChar(c);

                readValue(buffer.toString());
            }
            else
                System.out.println("Dommage !!!!");
        }
    }
    
    /**
    * Called by the parser when an tag has been parsed.
    * @param tag the parsed tag.
    */
    public abstract void readTag(Tag tag);
    
    /**
    * Called by the parser when the value of an element has been parsed.
    * @param vale the parsed value.
    */
    public abstract void readValue(String value);
    
    
    /*
    * Private methods. Internal use.
    */
    
    
    private void clearBuffer() {
        buffer = new StringBuffer();
    }
    private boolean isTag(char c) {
        boolean isTag = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
        return isTag;
    }

    private boolean isDigit(char c) {
        return (c >= '0') && (c <= '9');
    }

    private boolean isHexDigit(char c) {
        return (c >= '0') && (c <= '9') || (c >= 'A' && c <= 'F')
                || (c >= 'a' && c <= 'f');
    }

    private final static boolean isSeparator(char c) {
        return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r');
    }

    private void parseName(InputStream source) throws IOException {
        char c;
        
        c = getChar(source);
        while (isTag(c) || isDigit(c)) {
            buffer.append(c);
            c = getChar(source);
        }
        ungetChar(c);
    }

    private void parseSeparator(InputStream source) throws IOException {
        char c;

        while (true) {
            c = getChar(source);
            if ((c != ' ') && (c != '\r') && (c != '\t') && (c != '\n')) {
                ungetChar(c);
                break;
            }
        }
    }

    private void parseEqual(InputStream source) throws IOException {
        char c;

        parseSeparator(source);
        c = getChar(source);
        if (c != '=') {
            ungetChar(c);
            // Erreur
        }
        parseSeparator(source);
    }

    private void parseQuote(InputStream source) throws IOException {
        char c;

        c = getChar(source);
        if (c != '"' && c != '\'') {
            ungetChar(c);
            // Erreur
        }
    }

    private void parseClosing(InputStream source) throws IOException {
        char c;

        parseSeparator(source);
        if ((c = getChar(source)) == '>') {
            return;
        }
        else if (c == '/') {
            parseSeparator(source);
            if ((c = getChar(source)) == '>') {
                return;
            }
            else {
                ungetChar(c);
                // erreur
            }
        }
        else {
            ungetChar(c);
            // erreur
        }
    }

    private char getChar(InputStream source) throws IOException {
        int i;

        if (charRead == -1) {
            if ((i = source.read()) < 0) {
                throw new IOException("EOF");
            }
        }
        else {
            i = charRead;
            charRead = -1;
        }

        return (char) i;
    }

    private void ungetChar(char c) throws IOException {
        if (charRead == -1)
            charRead = (int) c;
        else
            throw new IOException("can't unget twice...");
    }
}
